/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.backends.rete;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import xowl.interpreter.backends.rdf.*;

/**
 *
 * @author Laurent WOUTERS
 */
public class AlphaGraph {
    private Map<RDFNode, Map<RDFNode, Map<RDFNode, AlphaMemory>>> map;
    
    public AlphaGraph() {
        map = new IdentityHashMap<RDFNode, Map<RDFNode, Map<RDFNode, AlphaMemory>>>();
    }
    
    public void fire(TripleFact fact) {
        RDFNode sub = fact.getTriple().getSubject();
        RDFNode prop = fact.getTriple().getProperty();
        RDFNode obj = fact.getTriple().getObject();
        AlphaMemory mem = match(null, null, null);
        if (mem != null) mem.activateFact(fact);
        mem = match(null, null, obj);
        if (mem != null) mem.activateFact(fact);
        mem = match(null, prop, null);
        if (mem != null) mem.activateFact(fact);
        mem = match(null, prop, obj);
        if (mem != null) mem.activateFact(fact);
        mem = match(sub, null, null);
        if (mem != null) mem.activateFact(fact);
        mem = match(sub, null, obj);
        if (mem != null) mem.activateFact(fact);
        mem = match(sub, prop, null);
        if (mem != null) mem.activateFact(fact);
        mem = match(sub, prop, obj);
        if (mem != null) mem.activateFact(fact);
    }
    public void unfire(TripleFact fact) {
        RDFNode sub = fact.getTriple().getSubject();
        RDFNode prop = fact.getTriple().getProperty();
        RDFNode obj = fact.getTriple().getObject();
        AlphaMemory mem = match(null, null, null);
        if (mem != null) mem.deactivateFact(fact);
        mem = match(null, null, obj);
        if (mem != null) mem.deactivateFact(fact);
        mem = match(null, prop, null);
        if (mem != null) mem.deactivateFact(fact);
        mem = match(null, prop, obj);
        if (mem != null) mem.deactivateFact(fact);
        mem = match(sub, null, null);
        if (mem != null) mem.deactivateFact(fact);
        mem = match(sub, null, obj);
        if (mem != null) mem.deactivateFact(fact);
        mem = match(sub, prop, null);
        if (mem != null) mem.deactivateFact(fact);
        mem = match(sub, prop, obj);
        if (mem != null) mem.deactivateFact(fact);
    }
    
    private Map<AlphaMemory, Collection<TripleFact>> buildDispatch(Collection<TripleFact> facts) {
        Map<AlphaMemory, Collection<TripleFact>> map = new IdentityHashMap<AlphaMemory, Collection<TripleFact>>();
        for (TripleFact fact : facts) {
            AlphaMemory[] mems = getMatches(fact);
            for (int i=0; i!=8; i++) {
                AlphaMemory mem = mems[i];
                if (mem == null)
                    continue;
                Collection<TripleFact> collec = map.get(mem);
                if (collec == null) {
                    collec = new ArrayList<TripleFact>();
                    map.put(mem, collec);
                }
                collec.add(fact);
            }
        }
        return map;
    }
    private AlphaMemory[] getMatches(TripleFact fact) {
        RDFNode sub = fact.getTriple().getSubject();
        RDFNode prop = fact.getTriple().getProperty();
        RDFNode obj = fact.getTriple().getObject();
        AlphaMemory[] result = new AlphaMemory[8];
        result[0] = match(null, null, null);
        result[1] = match(null, null, obj);
        result[2] = match(null, prop, null);
        result[3] = match(null, prop, obj);
        result[4] = match(sub, null, null);
        result[5] = match(sub, null, obj);
        result[6] = match(sub, prop, null);
        result[7] = match(sub, prop, obj);
        return result;
    }
    
    public void fire(Collection<TripleFact> facts) {
        Map<AlphaMemory, Collection<TripleFact>> dispatch = buildDispatch(facts);
        for (Entry<AlphaMemory, Collection<TripleFact>> entry : dispatch.entrySet())
            entry.getKey().activateFacts(new FastBuffer<TripleFact>(entry.getValue()));
    }
    public void unfire(Collection<TripleFact> facts) {
        Map<AlphaMemory, Collection<TripleFact>> dispatch = buildDispatch(facts);
        for (Entry<AlphaMemory, Collection<TripleFact>> entry : dispatch.entrySet())
            entry.getKey().deactivateFacts(new FastBuffer<TripleFact>(entry.getValue()));
    }
    
    private AlphaMemory match(RDFNode sub, RDFNode prop, RDFNode obj) {
        if (!map.containsKey(sub))
            return null;
        Map<RDFNode, Map<RDFNode, AlphaMemory>> mapProp = map.get(sub);
        if (!mapProp.containsKey(prop))
            return null;
        Map<RDFNode, AlphaMemory> mapObj = mapProp.get(prop);
        if (!mapObj.containsKey(obj))
            return null;
        AlphaMemory mem = mapObj.get(obj);
        return mem;
    }
    
    public AlphaMemory resolveMemory(RDFTriple triple, KnowledgeBase kb) {
        RDFNode subj = triple.getSubject();
        RDFNode prop = triple.getProperty();
        RDFNode obj = triple.getObject();
        if (subj.getNodeType() == RDFNodeType.Variable)
            subj = null;
        if (prop.getNodeType() == RDFNodeType.Variable)
            prop = null;
        if (obj.getNodeType() == RDFNodeType.Variable)
            obj = null;
        if (!map.containsKey(subj))
            map.put(subj, new IdentityHashMap<RDFNode, Map<RDFNode, AlphaMemory>>());
        Map<RDFNode, Map<RDFNode, AlphaMemory>> mapProp = map.get(subj);
        if (!mapProp.containsKey(prop))
            mapProp.put(prop, new IdentityHashMap<RDFNode, AlphaMemory>());
        Map<RDFNode, AlphaMemory> mapObj = mapProp.get(prop);
        if (mapObj.containsKey(obj))
            return mapObj.get(obj);
        
        AlphaMemory mem = new AlphaMemory();
        Collection<TripleFact> temp = new ArrayList<TripleFact>();
        if (mapObj.containsKey(null)) {
            for (TripleFact fact : mapObj.get(null).getFacts()) {
                if (fact.getTriple().getObject() == obj)
                    temp.add(fact);
            }
        } else {
            Iterator<TripleFact> iterator = kb.iterate(subj, prop, obj);
            while (iterator.hasNext())
                temp.add(iterator.next());
        }
        if (!temp.isEmpty())
            mem.activateFacts(new FastBuffer<TripleFact>(temp));
        mapObj.put(obj, mem);
        return mem;
    }
    
    public AlphaMemory getMemory(RDFTriple triple) {
        RDFNode subj = triple.getSubject();
        RDFNode prop = triple.getProperty();
        RDFNode obj = triple.getObject();
        if (subj.getNodeType() == RDFNodeType.Variable)
            subj = null;
        if (prop.getNodeType() == RDFNodeType.Variable)
            prop = null;
        if (obj.getNodeType() == RDFNodeType.Variable)
            obj = null;
        Map<RDFNode, Map<RDFNode, AlphaMemory>> mapProp = map.get(subj);
        Map<RDFNode, AlphaMemory> mapObj = mapProp.get(prop);
        return mapObj.get(obj);
    }
    
    public void removeMemory(RDFTriple triple) {
        RDFNode subj = triple.getSubject();
        RDFNode prop = triple.getProperty();
        RDFNode obj = triple.getObject();
        if (subj.getNodeType() == RDFNodeType.Variable)
            subj = null;
        if (prop.getNodeType() == RDFNodeType.Variable)
            prop = null;
        if (obj.getNodeType() == RDFNodeType.Variable)
            obj = null;
        Map<RDFNode, Map<RDFNode, AlphaMemory>> mapProp = map.get(subj);
        Map<RDFNode, AlphaMemory> mapObj = mapProp.get(prop);
        mapObj.remove(obj);
        if (mapObj.isEmpty()) {
            mapProp.remove(prop);
            if (mapProp.isEmpty())
                map.remove(subj);
        }
    }
    
    public void clear() {
        map = new IdentityHashMap<RDFNode, Map<RDFNode, Map<RDFNode, AlphaMemory>>>();
    }
}
